﻿using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using Microsoft.VisualStudio.VCProjectEngine;
using System.IO;
using VSLangProj80;
using VSLangProj;

namespace Gauss.Isp
{
    public class ProjectHandler
    {

        public enum ProjType
        {
            VC, //VC++
            CS, //C#
            FS, //F#
            UNKN //unknown
        }

        private ProjType _type;
        private Project _project;
        private Configuration _activeConfiguration;
        private VCProject _vcProject;
        private VCConfiguration _vcActiveConfiguration;
        private VCCLCompilerTool _vcCompilerTool;
        private VCLinkerTool _vcLinkerTool;
        private VCLibrarianTool _vcLibrarianTool;
        private bool _buildProject;

        private string _compilerIncludeDirectoriesBackup;
        private string _linkerDependenciesBackup;
        private string _linkerLibraryDirectoryBackup;
        private runtimeLibraryOption _compilerRuntimeLibraryBackup;
        private string _MPIDLLReferencePathBackup;
        private string _MPIDLLReferenceNameBackup;

        private string _activeConfigName;
        public string ActiveConfigName
        {
            get
            {
                return _activeConfigName;
            }
            set { }
        }

        private bool _changedCompilerIncludeDirectories;
        private bool _changedLinkerDependencies;
        private bool _changedLinkerLibraryDirectory;
        private bool _changedCompilerRuntimeLibrary;
        private bool _changedReference = false;

        public ProjType Type
        {
            get
            {
                return _type;
            }
            set { }
        }

        private List<object> _addedProjectFiles;

        public VCConfiguration VCActiveConfiguration
        {
            get { return _vcActiveConfiguration; }
        }

        public runtimeLibraryOption RuntimeLibrary
        {
            get { return _vcCompilerTool.RuntimeLibrary; }
        }

        public bool BuildProject
        {
            get { return _buildProject; }
            set { _buildProject = value; }
        }

        public string UniqueName
        {
            get { return _project.UniqueName; }
        }

        public ProjectHandler(Project project)
        {
            if (project == null)
            {
                throw new ArgumentNullException("project");
            }
            _project = project;
            _type = GetProjType(project);

            // Get the VCProject.
            _vcProject = project.Object as VCProject;

            // Get the active project configuration.
            _activeConfiguration = project.ConfigurationManager.ActiveConfiguration;
            if (_activeConfiguration == null)
            {
                throw new ApplicationException("Could not find the active configuration for project " + project.Name + ".");
            }
            _activeConfigName = _activeConfiguration.ConfigurationName + "|" + _activeConfiguration.PlatformName;
            if (_vcProject != null)
            {
                foreach (VCConfiguration config in (IVCCollection)_vcProject.Configurations)
                {
                    if (config.Name == _activeConfigName)
                    {
                        _vcActiveConfiguration = config;
                        break;
                    }
                }
                //if (_vcActiveConfiguration == null && !_isDotNet)
                //{
                //    throw new ApplicationException("Could not find the active configuration for project " + project.Name + ".");
                //}

                // Get the 'compiler tool' to access the compile parameters.
                _vcCompilerTool = ((IVCCollection)_vcActiveConfiguration.Tools).Item("VCCLCompilerTool") as VCCLCompilerTool;
                //if (_vcCompilerTool == null && !_isDotNet)
                //{
                //    throw new ApplicationException("Unable to access compiler settings for project " + project.Name + ".");
                //}

                // Get the 'linker tool' to access the linker parameters.
                _vcLinkerTool = ((IVCCollection)_vcActiveConfiguration.Tools).Item("VCLinkerTool") as VCLinkerTool;
                if (_vcLinkerTool == null && _type == ProjType.VC)
                {
                    _vcLibrarianTool = ((IVCCollection)_vcActiveConfiguration.Tools).Item("VCLibrarianTool") as VCLibrarianTool;
                    if (_vcLibrarianTool == null)
                    {
                        throw new ApplicationException("Unable to access linker settings for project " + project.Name + ".");
                    }
                }
            }

            this._buildProject = true;
            this._addedProjectFiles = new List<object>();
        }

        public void Save()
        {
            //if (!_isDotNet)
            //{
            //    _vcProject.Save();
            //}
            //else
            //{
                _project.Save();
            //}
        }

        public ProjectModification GetProjectModification()
        {
            return new ProjectModification(_project);
        }

        public VCDebugSettings GetDebugSettings()
        {
            if (_type != ProjType.VC) throw new ApplicationException(Resources.Message_CalledCFuncOnDotNet);
            return _vcActiveConfiguration.DebugSettings as VCDebugSettings;
        }

        public string GetExecutable()
        {
            // First, look in the linker output.
            string executable;
            if (_type == ProjType.VC)
            {
                executable = _vcActiveConfiguration.Evaluate(Linker_OutputFile).Trim();
            }
            else
            {
                List<string> projObjs = new List<string>();
                List<string> configPropObjs = new List<string>();
                foreach (Property p in _project.Properties)
                {
                    if (p.NumIndices == 0)
                    {
                        string value;
                        try
                        {
                            projObjs.Add(p.Name + ": " + p.Value.ToString());
                        }
                        catch (Exception e) { }
                    }
                }
                foreach (Property p in _project.ConfigurationManager.ActiveConfiguration.Properties)
                {
                    if (p.NumIndices == 0)
                    {
                        string value;
                        try
                        {
                            configPropObjs.Add(p.Name + ": " + p.Value.ToString());
                        }
                        catch (Exception e) { }
                    }
                }
                executable = _project.Properties.Item("FullPath").Value.ToString() + 
                    _project.ConfigurationManager.
                    ActiveConfiguration.Properties.Item("OutputPath").Value.ToString() + 
                     _project.Properties.Item("OutputFileName").Value.ToString();
            }
            if (!string.IsNullOrEmpty(executable))
            {
                if (_type == ProjType.VC && !File.Exists(executable))
                {
                    executable = _vcActiveConfiguration.Evaluate(Path.Combine("$(OutDir)", executable));
                    if (!File.Exists(executable))
                    {
                        executable = _vcActiveConfiguration.Evaluate(Path.Combine(Path.Combine("$(ProjectDir)", "$(OutDir)"), executable));
                        if (!File.Exists(executable))
                        {
                            executable = null;
                        }
                    }
                }
            }

            // If not found, look in the "Debug Settings" page for a path.
            if (string.IsNullOrEmpty(executable))
            {
                if (_type == ProjType.VC)
                {
                    VCDebugSettings debugSettings = GetDebugSettings();
                    if (debugSettings != null)
                    {
                        string appCommand = debugSettings.Command;
                        if (appCommand != null)
                        {
                            executable = _vcActiveConfiguration.Evaluate(appCommand);
                            if (!File.Exists(executable))
                            {
                                executable = _vcActiveConfiguration.Evaluate(Path.Combine("$(OutDir)", appCommand));
                                if (!File.Exists(executable))
                                {
                                    executable = _vcActiveConfiguration.Evaluate(Path.Combine(Path.Combine("$(ProjectDir)", "$(OutDir)"), appCommand));
                                    if (!File.Exists(executable))
                                    {
                                        executable = null;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // If still not found, look in the project's built files.
            if (string.IsNullOrEmpty(executable))
            {
                OutputGroup og = _project.ConfigurationManager.ActiveConfiguration.OutputGroups.Item("Built");
                foreach (string filePath in (object[])og.FileURLs)
                {
                    string file = filePath.ToString().Substring(@"file:///".Length);
                    if (File.Exists(file))
                    {
                        executable = file;
                        break;
                    }
                }
            }

            return executable;
        }

        public string GetWorkingDirectory()
        {
            if (_type != ProjType.VC)
            {
                string retVal;// =  _project.ConfigurationManager.ActiveConfiguration.
                //    Properties.Item("StartWorkingDirectory").Value.ToString();
                //if (retVal == null || retVal == string.Empty)
                //{
                    retVal = _project.Properties.Item("FullPath").Value.ToString() +
                    _project.ConfigurationManager.
                    ActiveConfiguration.Properties.Item("OutputPath").Value.ToString();
                //}
                return retVal;
            }
            else
            {
                VCDebugSettings debugSettings = GetDebugSettings();
                if (debugSettings == null)
                {
                    return _vcActiveConfiguration.Evaluate("$(ProjectDir)");
                }
                else
                {
                    string directory = _vcActiveConfiguration.Evaluate(debugSettings.WorkingDirectory);
                    if (!Directory.Exists(directory))
                    {
                        string workDir = debugSettings.WorkingDirectory;
                        if (workDir != null)
                            directory = _vcActiveConfiguration.Evaluate(Path.Combine("$(ProjectDir)", workDir));
                        if (!Directory.Exists(directory))
                            directory = _vcActiveConfiguration.Evaluate("$(ProjectDir)");
                    }
                    return directory;
                }
            }
        }

        public static ProjType GetProjType(Project p)
        {
            ProjectHandler.ProjType ptype;
            switch (p.Kind)
            {
                case "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}":
                    ptype = ProjectHandler.ProjType.CS;
                    break;
                case "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}":
                    ptype = ProjectHandler.ProjType.VC;
                    break;
                case "{f2a71f9b-5d33-465a-a702-920d77279786}":
                    ptype = ProjectHandler.ProjType.FS;
                    break;
                default:
                    ptype = ProjectHandler.ProjType.UNKN;
                    break;
            }
            return ptype;
        }

        public string GetProgramArguments()
        {
            if (_type != ProjType.VC)
            {
                string retVal = string.Empty;
                Properties props = _project.ConfigurationManager.ActiveConfiguration.
                    Properties;
                //F# projects only seem to have one property, NOT including StartArguments
                try
                {
                    retVal = props.Item("StartArguments").Value.ToString();
                }
                catch(Exception){}
                return retVal;
            }
            else
            {
                VCDebugSettings debugSettings = GetDebugSettings();
                if (debugSettings == null)
                {
                    return "";
                }
                else
                {
                    string args = debugSettings.CommandArguments;
                    if (string.IsNullOrEmpty(args))
                        return "";
                    else
                        return args;
                }
            }
        }

        public string FindHeaderPath(string header)
        {
            if (_type == ProjType.VC)
            {
                if (string.IsNullOrEmpty(header))
                {
                    throw new ArgumentNullException("header");
                }

                // First, search the include directories specified by the project.
                string searchPath = _vcCompilerTool.AdditionalIncludeDirectories;

                // Then search the platform include directories.
                VCPlatform vcPlatform = _vcActiveConfiguration.Platform as VCPlatform;
                if (vcPlatform != null)
                {
                    searchPath += ";" + vcPlatform.IncludeDirectories;
                }

                // Separate out the directories, seeing if the header file exists.
                string[] paths = searchPath.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                for (int i = 0; i < paths.Length; i++)
                {
                    paths[i] = _vcActiveConfiguration.Evaluate(paths[i].Trim(new char[] { '"' }).Trim());
                    string headerPath = Path.Combine(paths[i], header);
                    if (File.Exists(headerPath))
                    {
                        return headerPath;
                    }
                }

                return null;
            }
            else
            {
                return string.Empty;
            }
        }

        public void AddFileToProject(string file)
        {
            if (_type == ProjType.VC)
            {
                _addedProjectFiles.Add(_vcProject.AddFile(file));
            }
        }

        public void RestoreProjectFiles()
        {
            if (_type == ProjType.VC)
            {
                foreach (object file in _addedProjectFiles)
                {
                    _vcProject.RemoveFile(file);
                }
            }
        }

        public void AddCompilerIncludeDirectory(string directory)
        {
            if (_type == ProjType.VC)
            {
                // Clean-up the directory and add quotes if needed.
                if (string.IsNullOrEmpty(directory))
                    return;
                directory = directory.Trim().Trim(new char[] { '"', ' ' });
                if (string.IsNullOrEmpty(directory))
                    return;
                if (directory.Contains(" "))
                    directory = Utils.StringFormat("\"{0}\"", directory);

                // Make a backup.
                if (!_changedCompilerIncludeDirectories)
                {
                    _compilerIncludeDirectoriesBackup = _vcCompilerTool.AdditionalIncludeDirectories;
                    _changedCompilerIncludeDirectories = true;
                }

                if (string.IsNullOrEmpty(_vcCompilerTool.AdditionalIncludeDirectories))
                {
                    _vcCompilerTool.AdditionalIncludeDirectories = directory;
                }
                else
                {
                    // Clean-up the current include directories.
                    string[] directories = _vcCompilerTool.AdditionalIncludeDirectories.Split
                                                (new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                    for (int i = 0; i < directories.Length; i++)
                    {
                        directories[i] = directories[i].Trim();
                        if (!directories[i].Contains(" "))
                            directories[i] = directories[i].Trim(new char[] { '"', ' ' });
                        if (directories[i].ToUpperInvariant() == directory.ToUpperInvariant())
                            directories[i] = "";
                    }

                    // Change the settings.
                    string newPath = Utils.StringFormat("{0};{1}",
                                                        directory,
                                                        string.Join(";", directories))
                                          .Replace(";;", ";");
                    if (newPath.EndsWith(";"))
                        newPath = newPath.Substring(0, newPath.Length - 1);
                    _vcCompilerTool.AdditionalIncludeDirectories = newPath;
                }
            }
        }

        public void RestoreCompilerIncludeDirectories()
        {
            if (_type != ProjType.VC)
            {
                throw new ApplicationException(Resources.Message_CalledCFuncOnDotNet);
            }
            if (_changedCompilerIncludeDirectories)
            {
                _vcCompilerTool.AdditionalIncludeDirectories = _compilerIncludeDirectoriesBackup;
                _changedCompilerIncludeDirectories = false;
            }
        }

        private string Linker_AdditionalDependencies
        {
           get
            {
                if (_vcLinkerTool != null)
                    return _vcLinkerTool.AdditionalDependencies;
                else
                    return _vcLibrarianTool.AdditionalDependencies;
            }

            set
            {
                if (_vcLinkerTool != null)
                    _vcLinkerTool.AdditionalDependencies = value;
                else
                    _vcLibrarianTool.AdditionalDependencies = value;
            }
        }

        private string Linker_AdditionalLibraryDirectories
        {
           get
            {
                if (_vcLinkerTool != null)
                    return _vcLinkerTool.AdditionalLibraryDirectories;
                else
                    return _vcLibrarianTool.AdditionalLibraryDirectories;
            }

            set
            {
                if (_vcLinkerTool != null)
                    _vcLinkerTool.AdditionalLibraryDirectories = value;
                else
                    _vcLibrarianTool.AdditionalLibraryDirectories = value;
            }
        }

        private string Linker_OutputFile
        {
            get
            {
                string file;
                if (_vcLinkerTool != null)
                    file = _vcLinkerTool.OutputFile;
                else
                    file = _vcLibrarianTool.OutputFile;
                if (string.IsNullOrEmpty(file))
                    return "";
                else
                    return file;
            }

            set
            {
                if (_vcLinkerTool != null)
                    _vcLinkerTool.OutputFile = value;
                else
                    _vcLibrarianTool.OutputFile = value;
            }
        }

        public void AddLinkerDependency(string dependency)
        {
            if (_type == ProjType.VC)
            {
                // Clean-up the dependency and add quotes if needed.
                if (string.IsNullOrEmpty(dependency))
                    return;
                dependency = dependency.Trim().Trim(new char[] { '"', ' ' });
                if (string.IsNullOrEmpty(dependency))
                    return;
                if (dependency.Contains(" "))
                    dependency = Utils.StringFormat("\"{0}\"", dependency);

                // Make a backup.
                if (!_changedLinkerDependencies)
                {
                    _linkerDependenciesBackup = Linker_AdditionalDependencies;
                    _changedLinkerDependencies = true;
                }

                // Change the settings.
                if (string.IsNullOrEmpty(Linker_AdditionalDependencies))
                {
                    Linker_AdditionalDependencies = dependency;
                }
                else
                {
                    Linker_AdditionalDependencies = Utils.StringFormat("{0} {1}",
                                                                       dependency,
                                                                       Linker_AdditionalDependencies);
                }
            }
        }

        public void RestoreLinkerDependencies()
        {
            if (_type == ProjType.VC)
            {
                if (_changedLinkerDependencies)
                {
                    Linker_AdditionalDependencies = _linkerDependenciesBackup;
                    _changedLinkerDependencies = false;
                }
            }
        }

        public void AddLinkerLibraryDirectory(string directory)
        {
            if (_type == ProjType.VC)
            {
                // Clean-up the directory and add quotes if needed.
                if (string.IsNullOrEmpty(directory))
                    return;
                directory = directory.Trim().Trim(new char[] { '"', ' ' });
                if (string.IsNullOrEmpty(directory))
                    return;
                if (directory.Contains(" "))
                    directory = Utils.StringFormat("\"{0}\"", directory);

                // Make a backup.
                if (!_changedLinkerLibraryDirectory)
                {
                    _linkerLibraryDirectoryBackup = Linker_AdditionalLibraryDirectories;
                    _changedLinkerLibraryDirectory = true;
                }

                if (string.IsNullOrEmpty(Linker_AdditionalLibraryDirectories))
                {
                    Linker_AdditionalLibraryDirectories = directory;
                }
                else
                {
                    // Clean-up the current linker directories.
                    string[] directories = Linker_AdditionalLibraryDirectories.Split
                                                (new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                    for (int i = 0; i < directories.Length; i++)
                    {
                        directories[i] = directories[i].Trim();
                        if (!directories[i].Contains(" "))
                            directories[i] = directories[i].Trim(new char[] { '"', ' ' });
                        if (directories[i].ToUpperInvariant() == directory.ToUpperInvariant())
                            directories[i] = "";
                    }

                    // Change the settings.
                    string newPath = Utils.StringFormat("{0};{1}",
                                                        directory,
                                                        string.Join(";", directories))
                                          .Replace(";;", ";");
                    if (newPath.EndsWith(";"))
                        newPath = newPath.Substring(0, newPath.Length - 1);
                    Linker_AdditionalLibraryDirectories = newPath;
                }
            }
        }

        public void RestoreLinkerLibraryDirectories()
        {
            if (_type == ProjType.VC)
            {
                if (_changedLinkerLibraryDirectory)
                {
                    Linker_AdditionalLibraryDirectories = _linkerLibraryDirectoryBackup;
                    _changedLinkerLibraryDirectory = false;
                }
            }
        }

        public void ChangeRuntimeLibrary(runtimeLibraryOption newRuntime)
        {
            if (_type == ProjType.VC)
            {
                // Make a backup.
                if (!_changedCompilerRuntimeLibrary)
                {
                    _compilerRuntimeLibraryBackup = _vcCompilerTool.RuntimeLibrary;
                    _changedCompilerRuntimeLibrary = true;
                }

                _vcCompilerTool.RuntimeLibrary = newRuntime;
            }
        }

        public void RestoreRuntimeLibrary()
        {
            if (_type == ProjType.VC)
            {
                if (_changedCompilerRuntimeLibrary)
                {
                    _vcCompilerTool.RuntimeLibrary = _compilerRuntimeLibraryBackup;
                    _changedCompilerRuntimeLibrary = false;
                }
            }
        }

        public void ReplaceReference(FileInfo dllPath)
        {
            if (_type != ProjType.VC)
            {
                VSLangProj.References refs = (_project.Object as VSProject).References;
                foreach (VSLangProj.Reference r in refs)
                {
                    string name = dllPath.Name;
                    if (r != null && name.Remove(name.Length - 4) == r.Name)
                    {
                        if (!_changedReference)
                        {
                            _MPIDLLReferencePathBackup = r.Path;
                            _MPIDLLReferenceNameBackup = r.Name;
                        }
                        _changedReference = true;
                        r.Remove();
                        break;
                    }
                }
               
                Reference reference = refs.Add(dllPath.FullName);
                //reference.CopyLocal = false;
            }
        }

        public void RestoreReferences()
        {
            if (_changedReference)
            {
                VSLangProj.References refs = (_project.Object as VSProject).References;
                foreach (VSLangProj.Reference r in refs)
                {
                    if (_MPIDLLReferenceNameBackup == r.Name)
                    {
                        //String path = Path.Combine(GetWorkingDirectory(), r.Name + ".dll");
                        //if (File.Exists(path))
                        //{
                        //    File.Delete(path);
                        //}
                        r.Remove();
                        break;
                    }
                }
                refs.Add(_MPIDLLReferencePathBackup);
                _changedReference = false;
            }
        }

        //FOR DEBUG ONLY -- doesn't work with VC
        internal void DumpPropertyItems()
        {
            List<String> items = new List<string>();
            items.Add("Count: " + _project.ConfigurationManager.ActiveConfiguration.
                Properties.Count);
            foreach (EnvDTE.Property p in _project.ConfigurationManager.ActiveConfiguration.
                Properties)
            {
                items.Add(p.Name + ": " + p.Value.ToString());
            }
            foreach (EnvDTE.Property p in _project.Properties)
            {
                try
                {
                    items.Add(p.Name + ": " + p.Value.ToString());
                }
                catch (Exception) { }
            }
            System.Diagnostics.Debugger.Break();           
        }
    }



    public class ProjectModification
    {
        private Dictionary<string, DateTime> _lastModifiedDates;
        private Project _project;
        private string _activeConfigName;

        public ProjectModification(Project project)
        {
            _lastModifiedDates = new Dictionary<string, DateTime>();
            _project = project;
            Update();
        }

        public void Update()
        {
            _lastModifiedDates.Clear();
            Configuration config = _project.ConfigurationManager.ActiveConfiguration;
            if (config == null)
            {
                _activeConfigName = null;
                return;
            }
            else
            {
                _activeConfigName = config.ConfigurationName + "|" + config.PlatformName;
            }

            foreach (OutputGroup og in config.OutputGroups)
            {
                if (og.CanonicalName == "Symbols")
                    continue;

                foreach (object filePath in (object[])og.FileURLs)
                {
                    try
                    {
                        string path = filePath.ToString().Substring("file:///".Length);
                        FileInfo file = new FileInfo(path);
                        _lastModifiedDates.Add(path.ToUpperInvariant(), file.LastWriteTimeUtc);
                    }
                    catch { }
                }
            }
        }

        public bool ProjectChanged()
        {
            Configuration config = _project.ConfigurationManager.ActiveConfiguration;
            if (config == null)
            {
                if (_activeConfigName == null)
                    return false;
                else
                    return true;
            }
            string configName = config.ConfigurationName + "|" + config.PlatformName;
            if (configName != _activeConfigName)
                return true;

            int count = 0;
            foreach (OutputGroup og in config.OutputGroups)
            {
                if (og.CanonicalName == "Symbols")
                    continue;

                foreach (object filePath in (object[])og.FileURLs)
                {
                    try
                    {
                        string path = filePath.ToString().Substring("file:///".Length);
                        if (!_lastModifiedDates.ContainsKey(path.ToUpperInvariant()))
                            return true;
                        FileInfo file = new FileInfo(path);
                        if (_lastModifiedDates[path.ToUpperInvariant()] != file.LastWriteTimeUtc)
                            return true;
                        count++;
                    }
                    catch { }
                }
            }

            if (count != _lastModifiedDates.Count)
                return true;
            else
                return false;
        }
    }
}
